package com.common.util.javers;

import com.common.util.javers.process.I18NProcess;
import com.common.util.javers.process.LogProcess;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.javers.core.Javers;
import org.javers.core.diff.Change;
import org.javers.core.metamodel.type.EntityType;
import org.javers.repository.jql.JqlQuery;
import org.javers.repository.jql.QueryBuilder;
import org.javers.spring.auditable.AspectUtil;
import org.springframework.stereotype.Component;
import sun.misc.Unsafe;

import javax.annotation.PostConstruct;
import javax.faces.context.FacesContext;
import javax.persistence.Transient;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.List;
/**
 * Created by Kenti on 2015/11/16.<br/>
 *
 * 该类用来捕捉 javers audit 切面<br/>
 * 来实现更新的日志记录<br/>
 *
 * 通常做法:<br/>
 * 1.找到模块相关的实体类 或者该父类，然后 在对象引用字段上加 diffingnore注解<br/>
 * 2.找到service类，分别在 get,delete等方法上加 @Auditable(操作类型)<br/>
 * 3.在i18ntest包中，运行国际化操作。将实体类，及action的国际化信息输出<br/>
 * 4.完善国际化信息<br/>
 * 5.测试<br/>
 *
 */
@Aspect
@Log4j
@Getter
@Setter
public class JaversCommitProcessor {

    public static ThreadLocal<String> t=new ThreadLocal<>();

    private Javers javers;

    private LogProcess logProcess;

    /**
     * 用来处理国际化的
     */
    private I18NProcess i18NProcess;

    /**
     * 进行controller的捕捉
     * 比如 com.xx.xx.controller.menu.do?deleteId=1
     * 就写 com.xx.xx.controller 即可
     */
    private String[] menuList;

    /**
     * 初始化检查
     */
    @PostConstruct
    public void init() throws Exception{
        if (i18NProcess==null || menuList==null || menuList.length==0){
            //throw new Exception("Invalid property. Please provide i18NProcess and menuList to continue.");
        }
    }

    /**
     * 使用around 意味如果更新方法执行异常 则不进行日志记录
     * @param pjp
     */

    @Around("@annotation(Auditable)")//@annotation(Auditable)
    public Object commitAdvice(ProceedingJoinPoint pjp) {
        Object proceedValue=null;
        MethodSignature methodSignature= (MethodSignature)pjp.getSignature();
        boolean isMethodVoid="void".equals(methodSignature.getReturnType().getName().toLowerCase());
        String methodName=methodSignature.getName();
        try {
            proceedValue=pjp.proceed();
        }
        catch (Throwable e) {
            log.debug("error invoke "+pjp.getTarget().getClass()+"."+methodName+" details:"+e);
        }

        try{
            Auditable auditable= pjp.getTarget().getClass().getMethod(methodSignature.getMethod().getName(),methodSignature.getMethod().getParameterTypes()).getAnnotation(Auditable.class);//methodSignature.getMethod().getAnnotation(Auditable.class);
            if (auditable!=null) {
                //获取该方法所属的操作类型
                if (auditable.value() == OperationModule.GET && proceedValue != null) {
                    processGet(proceedValue,pjp,methodName,isMethodVoid,auditable);
                } else if (auditable.value() == OperationModule.ADDORUPDATE) {
                    processAddOrUpdate(proceedValue,pjp,methodName,isMethodVoid,auditable);
                } else if (auditable.value() == OperationModule.DELETE) {
                    processDelete(proceedValue,pjp,methodSignature,methodName,isMethodVoid,auditable);
                } else if (auditable.value() == OperationModule.UPDATE) {
                    processUpdate(proceedValue,pjp,methodName,isMethodVoid,auditable);
                }else if (auditable.value()==OperationModule.OTHER){
                    //其他操作
                }
            }
        }catch (Exception e){
            log.error("error while doing commitprocess :"+e);
        }
        return proceedValue;
    }

    /**
     * get方法
     * @param proceedValue
     * @param pjp
     * @param methodName
     * @param isVoidMethod
     * @param auditable
     */
    protected void processGet(Object proceedValue, ProceedingJoinPoint pjp, String methodName, boolean isVoidMethod, Auditable auditable) {
        if (proceedValue==null){
            log.error("no proceedValue to commit on "+pjp.getTarget().getClass()+"."+methodName);
        }else{
            javers.commit("user", proceedValue);
        }
    }

    //删除
    protected void processDelete(Object proceedValue, ProceedingJoinPoint pjp, MethodSignature methodSignature, String methodName, boolean isVoidMethod, Auditable auditable) {
            Class<?>[] clazz= methodSignature.getMethod().getParameterTypes();
            StringBuffer ids=new StringBuffer();
            if (clazz.length>0&& clazz[0].isArray()){
                //以数组的形式删除数据
                //        public ShopMember[] deleteByIds(Long[] ids) {
                Object[] objects= (Object[]) pjp.getArgs()[0];
                for (int i=0;i<objects.length;i++){
                    ids.append(objects[i]).append(" ");
                }
            }else{
                //单个删除数据
                //public Commerce deleteById(Long id) {
                ids.append(pjp.getArgs()[0]);
            }
            String[] modules=getModuleName();
            log.debug(methodName+" "+methodSignature.getMethod().getParameters()[0].getName()+"="+ids.toString());
            logProcess.doLog(modules[0],ids.toString(),modules[1]);
    }

    ////更新或添加
    protected void processAddOrUpdate(Object proceedValue, ProceedingJoinPoint pjp, String methodName, boolean isVoidMethod, Auditable auditable) {
        Object o=proceedValue;

        boolean isBasicType=false;
        try {
            isBasicType=((Class)o.getClass().getField("TYPE").get(null)).isPrimitive();
        }catch (Exception e){}

        if (isVoidMethod || isBasicType){
            Iterator var3 = AspectUtil.collectArguments(pjp).iterator();
            if (var3.hasNext()) {
                o= var3.next();
            }else{
                log.error("there is no param to commit on "+pjp.getTarget().getClass()+"."+methodName);
                return;
            }
        }else if (o==null){
            log.error("there is no proceedValue to commit on "+pjp.getTarget().getClass()+"."+methodName);
            return;
        }
        Object id=getObjectId(o);
        QueryBuilder queryBuilder = QueryBuilder.byInstanceId(id, o.getClass());
        JqlQuery jqlQuery = queryBuilder.build();
        List list=javers.findSnapshots(jqlQuery);
        if (list.size()>0){
            //修改
            update(o,pjp,methodName,isVoidMethod);
        }else{
            javers.commit("user", o);
            String[] modules=getModuleName();
            logProcess.doLog(modules[0],id+"",modules[1]);
        }
    }

    //更新
    protected void processUpdate(Object proceedValue, ProceedingJoinPoint pjp, String methodName, boolean isVoidMethod, Auditable auditable) {
        Object o=proceedValue;
        if (isVoidMethod){
            Object[] args=pjp.getArgs();
            if (args!=null && args.length>0) {
                o= args[auditable.pindex()];
            }else{
                log.error("there is no param to commit on "+pjp.getTarget().getClass()+"."+methodName);
                return;
            }
        }else if (o==null){
            log.error("there is no proceedValue to commit on "+pjp.getTarget().getClass()+"."+methodName);
            return;
        }
        if (o instanceof Object[]){
            Object[] oarray=(Object[])o;
            String[] moduleName=getModuleName();
            StringBuffer sb=new StringBuffer();
            for (int i=0;i<oarray.length;i++){
                sb.append(oarray[i]).append(",");
            }
            logProcess.doLog(moduleName[0], sb.toString(),moduleName[1]);
        }else{
            update(o,pjp,methodName,isVoidMethod);
        }
    }



    private void update(Object o,ProceedingJoinPoint pjp,String methodName,boolean isVoidMethod){
        // 先提交数据
        javers.commit("user", o);
        //处理
        Object id = getObjectId(o);
        QueryBuilder queryBuilder = QueryBuilder.byInstanceId(id, o.getClass());
        JqlQuery jqlQuery = queryBuilder.build();
        List<Change> changes = javers.findChanges(jqlQuery);
        changes.sort(((o1, o2) -> -1 * o1.getCommitMetadata().get().getCommitDate().compareTo(o2.getCommitMetadata().get().getCommitDate())));
        //处理得出的更改
        if (changes.size() > 0) {
            //每次更新完 清空版本库 以防止占内存及 下次更新获取历史时造成的
            JaversFactory.inMemoryRepository.commitDelete(changes.get(0).getAffectedGlobalId());
            //做日志
            String[] moduleName=getModuleName();

            logProcess.doLog(moduleName[0], changes.get(0).getAffectedLocalId() + "", moduleName[1], o, changes);
            if(log.isDebugEnabled()){
                log.debug("got changes for "+o+" :"+changes);
            }
        }
    }

    private Object getObjectId(Object o) {
        EntityType entityType = javers.getTypeMapping(o.getClass());
        return entityType.getIdProperty().get(o);
    }

    /**
     * 获取当前执行的菜单名和方法名
     * @return String[0] 模块名 String[1] 方法名
     */
    public String[] getModuleName(){
        String moduleName="no menu";
        String methodName="no method";
        StackTraceElement[] stackTraceElements=Thread.currentThread().getStackTrace();
        for (int i=0;i<stackTraceElements.length;i++){
            StackTraceElement stackTraceElement=stackTraceElements[i];
            for (int j=0;j<menuList.length;j++){
                if (stackTraceElement.getClassName().contains(menuList[j])){
                    moduleName=stackTraceElement.getClassName();
                    methodName=stackTraceElement.getMethodName();
                    //TODO 在这里可以过滤功能名
                    moduleName= getProperyI18N(stackTraceElement.getClassName(),moduleName);
                    methodName=getProperyI18N(stackTraceElement.getClassName()+"."+stackTraceElement.getMethodName(),methodName);
                    i=stackTraceElements.length;
                    break;
                }
            }
        }
        return new String[]{moduleName,methodName};
    }

    private String getProperyI18N(String key, String defaultVar, Object... params) {
        return i18NProcess.getProperyI18N(key,defaultVar,params);
    }
}
